package com.fyz.aaronfjava.common.aop;

import com.alibaba.fastjson.JSON;
import com.fyz.aaronfjava.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

/**
 * @Author：AaronF
 * @Date：2024/1/24 16:27
 * @description 方法日志增强 为{@link MethodLogging}提供支持
 */
@Aspect
@Component
public class MethodLoggingAspect {

    private static final Logger log = LoggerFactory.getLogger(MethodLoggingAspect.class);


    @Around(value = "@annotation(methodLogging)")
    public Object intercept(ProceedingJoinPoint joinPoint, MethodLogging methodLogging) throws Throwable {
        // 方法描述
        String methodDesc = methodLogging.value();
        // 是否打印参数
        boolean logArg = methodLogging.arg();
        // 是否打印耗时
        boolean logDuration = methodLogging.duration();
        // 是否打印返回值
        MethodSignature sign = (MethodSignature) joinPoint.getSignature();
        Class<?> returnType = sign.getReturnType();
        boolean logRetVal = !Void.TYPE.equals(returnType) && methodLogging.retVal();
        // 切入
        return intercept(joinPoint, methodDesc, logArg, logRetVal, logDuration);
    }

    public Object intercept(ProceedingJoinPoint jp, String methodDesc, boolean logArg,
                            boolean logRetVal, boolean logDuration) throws Throwable {
        // 启用执行耗时侦测
        long startTimestamp = logDuration ? System.currentTimeMillis() : 0L;
        // 方法描述, 未指定时取方法名
        methodDesc = StringUtil.isBlank(methodDesc) ? jp.getSignature().getName() : methodDesc;
        // 入参打印 打印级别为info以上
        if (logArg && log.isInfoEnabled()) {
            printArgs(jp, methodDesc);
        }
        try {
            // 方法执行
            Object result = jp.proceed();
            afterMethodReturn(logRetVal, logDuration, startTimestamp, methodDesc, result);
            return result;
        } catch (Throwable e) {
            // 打印耗时
            if (logDuration) {
                log.error("异常结束 {}, 耗时: {} ms, 异常信息: {}", methodDesc,
                        System.currentTimeMillis() - startTimestamp, e.getMessage());
            } else {
                log.error("异常结束 {}, 异常信息: {}", methodDesc, e.getMessage());
            }
            throw e;
        }
    }

    /**
     * 打印执行结束信息
     */
    private void afterMethodReturn(boolean logRetVal, boolean logDuration, long startTimestamp,
                                   String methodDesc, Object result) {
        // 不打印返回值
        if (!logRetVal) {
            // 打印耗时
            if (logDuration && log.isInfoEnabled()) {
                log.info("结束 {}, 耗时: {} ms ", methodDesc, System.currentTimeMillis() - startTimestamp);
            }
            return;
        }
        // 打印返回值
        if (logDuration && log.isInfoEnabled()) {
            // 打印耗时
            log.info("结束 {}, 耗时: {} ms, 返回值: {}", methodDesc, System.currentTimeMillis() - startTimestamp, resolveRet(result));
        } else if (log.isInfoEnabled()) {
            log.info("结束 {}, 返回值: {}", methodDesc, resolveRet(result));
        }
    }

    private String resolveRet(Object result) {
        if (result instanceof String) {
            return (String) result;
        }
        try {
            return JSON.toJSONString(result);
        } catch (Exception e) {
            log.error("方法返回参数转换json字符串失败", e);
            return null;
        }
    }

    /**
     * 打印执行信息
     * @param jp 切入点
     * @param methodDesc 方法参数
     */
    private void printArgs(ProceedingJoinPoint jp, String methodDesc) {
        Object[] args = jp.getArgs();
        if (args == null || args.length == 0) {
            log.info("执行 {}", methodDesc);
        } else {
            log.info("执行 {}, 参数: {}", methodDesc, resolveArg(args));
        }
    }

    /**
     * 解析参数为json串
     * @param args 参数数组
     * @return
     */
    private String resolveArg(Object[] args) {
        try {
            StringBuilder builder = new StringBuilder("[");
            for (Object o : args) {
                if (o instanceof String) {
                    builder.append(o).append(", ");
                } else {
                    builder.append(JSON.toJSONString(o)).append(", ");
                }
            }
            // 去除末尾的逗号和空格
            builder.delete(Math.max(builder.length() - 2, 0), builder.length());
            builder.append(']');
            return builder.toString();
        } catch (Exception e) {
            log.error("方法参数转换json字符串失败", e);
            return null;
        }
    }
}